package com.tianji.learning.schedule;

import com.tianji.common.utils.CollUtils;
import com.tianji.learning.constants.LearningConstants;
import com.tianji.learning.constants.RedisConstants;
import com.tianji.learning.entity.po.PointsBoard;
import com.tianji.learning.service.IPointsBoardSeasonService;
import com.tianji.learning.service.IPointsBoardService;
import com.tianji.learning.utils.TableInfoContext;
import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;

/**
 * 定时任务
 * 查询redis中学霸天梯榜，将前100名的学生信息存入redis
 * 每个月的1号0点0分0秒执行
 * <p>
 * 注意这里面是有顺序的，先创建表，再存入数据库，在删除redis中的数据
 */
@Component
@Slf4j
@RequiredArgsConstructor
public class PointsBoardPersistentScheduled {

    private final IPointsBoardSeasonService pointsBoardSeasonService;

    private final IPointsBoardService pointsBoardService;

    private final StringRedisTemplate stringRedisTemplate;

    /**
     * 创建上赛季(上个月) 的学霸天梯榜
     */
    // @Scheduled(cron = "0 0 3 1 * ?") // 每月1号，凌晨3点执行
    // 使用xxljob
    @XxlJob("createTableJob")
    public void createPointsBoardTableOfLastSeason() {
        log.info("创建上赛季(上个月) 的学霸天梯榜  任务执行了");
        // 1.获取上月时间
        // 如果是LocalDate就没有时分秒，如果是LocalDateTime就有时分秒
        LocalDate time = LocalDate.now().minusMonths(1);

        // 2.查询赛季表获取赛季id
        Integer seasonId = pointsBoardSeasonService.querySeasonByTime(time);

        if (seasonId == null) {
            log.info("上赛季(上个月) 的学霸天梯榜  任务执行失败，没有找到赛季id");
            return;
        }

        // 3.创建表  points_board_sessionId
        pointsBoardService.createPointsBoardTableBySeason(seasonId);
    }

    /**
     * 将上一个学霸天梯榜数据存入数据库
     * 根据不同的赛季存入不同的分表里面去
     */
    @XxlJob("savePointsBoard2DB") // jobHandler="savePointsBoard2DB"
    public void savePointsBoard2DB() {
        log.info("将上一个学霸天梯榜数据存入数据库  任务执行了");
        // 1.获取上月时间
        // 如果是LocalDate就没有时分秒，如果是LocalDateTime就有时分秒
        LocalDate time = LocalDate.now().minusMonths(1);

        // 查询赛季表获取赛季id
        Integer seasonId = pointsBoardSeasonService.querySeasonByTime(time);

        if (seasonId == null) {
            log.info("将上一个学霸天梯榜数据存入数据库  任务执行失败，没有找到赛季id");
            return;
        }

        // 3.计算动态表名
        String tableName = LearningConstants.POINTS_BOARD_TABLE_PREFIX + seasonId;

        // 4.将动态表明存入ThreadLocal
        TableInfoContext.setInfo(tableName);

        // 5.分页查询redis中的数据(不要一次性全部查询出来，因为数据量太大，会导致内存溢出)

        // 5.1 获取redis中的key  key = points_board_202108  年月
        String key = RedisConstants.POINTS_BOARD_KEY_PREFIX + time.format(DateTimeFormatter.ofPattern("yyyyMM"));

        int shardIndex = XxlJobHelper.getShardIndex(); // 每一个的服务器的索引
        int shardTotal = XxlJobHelper.getShardTotal(); // 总共的服务器数量

        // 这样在集群下面
        // 比如有两个服务器，那么shardIndex就是0和1，shardTotal就是2
        // 第一个服务器 shardIndex = 0  那么他处理的页数为 1,3,5,7
        // 第二个服务器 shardIndex = 1  那么他处理的页数为 2,4,6,8
        // 这样就能分开来处理了

        // int pageNo = 1; // 页码
        int pageNo = shardIndex + 1; // 页码
        int pageSize = 5; // 每页条数
        while (true) {
            List<PointsBoard> pointsBoards = pointsBoardService.queryCurrentSeason(key, pageNo, pageSize);
            if (CollUtils.isEmpty(pointsBoards)) {
                break; // 跳出循环
            }
            // 不为空，就存入数据库
            // 6.将redis中的数据存入数据库
            // 将rank的字段赋值到id上面，然后rank为空
            for (PointsBoard pointsBoard : pointsBoards) {
                pointsBoard.setId(pointsBoard.getRank().longValue());
                pointsBoard.setRank(null);
            }
            // 这里就不用传sessionId了，因为在ThreadLocal中已经存入了
            pointsBoardService.savePointsBoard2DB(pointsBoards);
            // pageNo++;
            // 5.翻页，跳过N个页，N就是分片数量
            pageNo += shardTotal;

        }

        // 7.将ThreadLocal中的数据清空
        TableInfoContext.remove();
    }


    /**
     * 清理Redis缓存任务
     * 将上个月的学霸天梯榜数据从redis中删除
     */
    @XxlJob("clearPointsBoardFromRedis")
    public void clearPointsBoardFromRedis() {
        // 1.获取上月时间
        LocalDateTime time = LocalDateTime.now().minusMonths(1);

        // 2.计算key
        String key = RedisConstants.POINTS_BOARD_KEY_PREFIX + time.format(DateTimeFormatter.ofPattern("yyyyMM"));

        // 3.删除
        stringRedisTemplate.unlink(key);
        //stringRedisTemplate.delete(key);

        // delete和unlink的区别
        // delete是直接删除，如果删除的是一个大key，那么会阻塞redis
        // delete不会释放内存，只是标记删除
        // delete可以返回删除的个数

        // unlink是异步删除，不会阻塞redis
        // unlink会释放内存
        // unlink不会返回删除的个数

        // 总结：如果删除的是一个大key，那么使用unlink，否则使用delete

    }

}
